/*
Copyright (c) 2009, Dan Hagerstrand and contributors
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of Cirrus Creative nor the names of its
  contributors may be used to endorse or promote products derived from this
  software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef BENG_MANAGER_H
#define BENG_MANAGER_H

/// The BengManager class serves as a sort of container for holding game objects using STL container classes such as std::list and std::vector.
/// First, you instantiate an object of type T_O, and then add an entry for it in the list using addEntry().
/// Once you add the object, it will be controlled by the BengManager for the rest of the object's lifetime.
///
/// Managing objects using this class simplifies handling of game logic since it provides facilities for iteration and safe deletion.
///
/// The template takes two class parameters: T_C being the STL-compliant container class and T_O being the object class for which entries are stored:\n
/// BengManager<std::list<MyObject*>, MyObject> myObjectList;

template <typename T_C, typename T_O>
class BengManager {

    private:

    bool releaseAllFlag_;
    std::vector<T_O*> releaseList_;

    protected:

    T_C objectList;
    typename T_C::iterator objectIterator;

    /// In order to deal with multiple managers holding entries to a single object, each manager must be able to handle safe deletion according to
    /// reference count as well as release from individual managers. Every manager holds an internal list of entries that are to be removed from
    /// that manager at some point. For objects that haven't been safely removed from the manager yet, a lookup can be performed to determine if
    /// object processing should be skipped because was marked as released. This method returns true if the given object is in the internal release list.
    bool wasJustReleased(T_O* object)
    {
        typename std::vector<T_O*>::iterator temp;

        // Looks to see if the object exists in the release list
        temp = find(this->releaseList_.begin(), this->releaseList_.end(), object);

        if (temp != this->releaseList_.end())
            return true;
        else
            return false;
    }

    public:

    BengManager(void)
    {
        this->releaseAllFlag_ = false;
    }
    ~BengManager(void)
    {
        // Purge the entries using the already existing function. No need for code duplication.
        this->releaseAllFlag_ = true;
        this->objectIterator = this->objectList.end(); // purgeEntries() won't work unless it sees that the iterator is at the end.
        this->purgeEntries();
    }

    /// Takes the pointer to an instantiated object of type T_O and adds it to the internal list. For the rest of the object's lifetime,
    /// it will be managed by the BengManager and will rely on it for safe deletion.
    void addEntry(T_O* object)
    {
        this->objectList.push_back(object);
        object->incrementRefCount(1); // One more manager is managing this object now
    }
    /// Marks the entry for release by adding it to an internal list. This differs from flagging the object for deletion in that you are only removing
    /// a reference from one manager, decrementing that object's reference count. When purgeEntries() is called, any marked entries will be removed from
    /// the current manager. If you are relocating the object to another manager and you have released that object from all managers to do so, you must
    /// add it as an entry to the new manager before the next call to purgeEntries(), or else the object itself will be physically deleted to prevent
    /// memory leaks.
    void releaseEntry(T_O* object)
    {
        typename std::vector<T_O*>::iterator temp;

        // Does the object already have an entry in the release list?
        temp = find(this->releaseList_.begin(), this->releaseList_.end(), object);

        // If not, add it!
        if (temp == this->releaseList_.end())
            releaseList_.push_back(object);
    }
    /// This function will tell the manager to release all objects and clear the list of entries. It is similar to releaseEntry() except that it simply
    /// sets an internal flag, stating the intent to release every object from the current manager. Also, object iteration will continue even after a call
    /// to this function.
    void releaseAll(void)
    {
        this->releaseAllFlag_ = true;
    }
    /// Returns the total number of objects being managed by the BengManager's internal list.
    int getCount(void);

    /// Sets the iterator at the beginning of the list and then returns the first object not marked for release or deletion, unless the list is
    /// empty or all objects are individually marked, in which case NULL is returned. You should call this function every time before you begin iterating
    /// through your objects.
    T_O* gotoStart(void)
    {
        T_O *obj = NULL;

        // Without this check, segment violations tend to occur. In this case, it can't hurt to check for sanity.
        if (this->objectList.size() == 0)
            return NULL;

        this->objectIterator = this->objectList.begin();

        obj = *this->objectIterator;

        // If the object that was found is marked for deletion or is being released from this manager, keep looking until a non-marked entry is found.
        while (obj->isMarkedForDeletion() || this->wasJustReleased(obj))
        {
            ++this->objectIterator;
            obj = *this->objectIterator;

            // If no suitable entries are found, cop out and return NULL.
            if (this->objectIterator == this->objectList.end())
                return NULL;
        }

        // Otherwise, return the object's pointer.
        return *this->objectIterator;
    }
    /// Advances the iterator and then grabs a pointer to the object at the iterator's next position, skipping any objects marked for release
    /// or deletion. This function returns NULL when at the end of the list, so it is a good idea to use a while loop to control iteration.
    T_O* iterate(void)
    {
        T_O *obj = NULL;

        // Sanity check.
        if (this->objectList.size() == 0)
            return NULL;

        // Keep iterating forward until a non-marked entry is found.
        do
        {
            ++this->objectIterator;

            // If no suitable entries are found, give up and return NULL.
            if (this->objectIterator == this->objectList.end())
                return NULL;

            obj = *this->objectIterator;

        } while (obj->isMarkedForDeletion() || this->wasJustReleased(obj));

        // Otherwise, return the object's pointer.
        return *this->objectIterator;
    }
    /// Returns true if the manager's iterator has reached the end (that is, has finished an iteration but is not in the middle of another one).
    /// Otherwise, returns false.
    bool hasFinishedIteration(void)
    {
        if (this->objectIterator == this->objectList.end())
            return true;
        else
            return false;
    }

    /// This function will perform the internal deletion/release routines. Any objects marked for deletion or release will be processed at some point
    /// provided that the function is called continuously. The function will bail out if it senses that the iterator has not reached the end,
    /// since purging entries mid-iteration is dangerous. Therefore, you should place this function after your iteration code.
    void purgeEntries(void)
    {
        T_O *obj = NULL;

        if (this->objectIterator != this->objectList.end())
            return; // TODO: Error display

        if (this->releaseAllFlag_)
        {
            this->objectIterator = this->objectList.begin();

            // Keep going until the end of the list is reached
            while (this->objectIterator != this->objectList.end())
            {
                obj = *this->objectIterator;

                // Object has one less reference now that it's released.
                obj->incrementRefCount(-1);

                // If there are no more references, delete the object
                if (obj->getRefCount() <= 0)
                    delete *this->objectIterator;

                ++this->objectIterator;
            }
            this->objectIterator = this->objectList.begin();

            // Wait until iteration is over before actually deleting the entries
            this->objectList.clear();
            this->releaseList_.clear();
        }
        else
        {
            // Clear out the list of released objects
            while (this->releaseList_.size() > 0)
            {
                typename T_C::iterator temp;

                obj = *this->releaseList_.rbegin();

                // Look for each released object within the list.
                temp = find(this->objectList.begin(), this->objectList.end(), obj);

                // If a result was found...
                if (temp != this->objectList.end())
                {
                    this->objectList.erase(temp);

                    obj->incrementRefCount(-1);

                    // If there are no more references, then theoretically it's safe to delete. That is, unless the programmer is doing
                    // weird things like using a custom system to manage objects elsewhere and doesn't set the ref count appropriately.
                    // But hey, it's not like C++ is the safest language in the world anyway.
                    if (obj->getRefCount() <= 0)
                        delete obj;
                }

                this->releaseList_.pop_back();

            }

            // Prepare object iterator
            this->objectIterator = this->objectList.begin();

            // Clear out any deleted objects, physically destroying them if no other manager is holding references anymore.
            while (this->objectIterator != this->objectList.end())
            {
                obj = *this->objectIterator;

                // Self-explanatory.
                if (obj->isMarkedForDeletion())
                {
                    this->objectIterator = this->objectList.erase(this->objectIterator);
                    obj->incrementRefCount(-1);

                    // All done!
                    if (obj->getRefCount() <= 0)
                        delete obj;
                }
                else
                    ++this->objectIterator;
            }
        }
    }

};

#endif
